#include "vmidi_rtmidi.h"

namespace VMidi {

void rtMidiCallback (double timeStamp, std::vector< unsigned char > *message, void
        *userData) {
  VMidiRtMidiThruCallback* cb = static_cast<VMidiRtMidiThruCallback*>(userData);
  cb->callback(timeStamp,message,NULL);
}

// Methods under critical section.
// {
void VMidiRtMidiThruCallback::
callback (double timeStamp, std::vector< unsigned char > *message, void
        *userData) {

  // Thru data to out.
  
  pthread_mutex_lock(&mutex);
  { // CRITICAL SECTION
    for (set<RtMidiOut*>::iterator it = out_.begin();
        it != out_.end();
        ++it) {
      /*cout << "Redirecting message :";
      for (vector<unsigned char>::iterator v_it = message->begin();
           v_it != message->end();
           ++v_it) {
        if (v_it != message->begin()) {
          cout << " ";
        }
        cout << (int)(*v_it);
      }
      cout << endl;*/
      (*it)->sendMessage (message);
    }
  }
  pthread_mutex_unlock(&mutex);
}

void VMidiRtMidiThruCallback::
addOutput (RtMidiOut* out) {
  pthread_mutex_lock(&mutex);
  { // CRITICAL SECTION
    out_.insert(out);
  }
  pthread_mutex_unlock(&mutex);
}

void VMidiRtMidiThruCallback::
removeOutput (RtMidiOut* out) {
  pthread_mutex_lock(&mutex);
  { // CRITICAL SECTION
    out_.erase(out);
  }
  pthread_mutex_unlock(&mutex);
}

//  }

void VMidiRtMidi::
createVirtualPort (VMidiPortType type, const string& name, int& portId) throw(string) {
  void* port = NULL;

  if (type == VMIDI_PORT_INPUT) {
    try {
      port = new RtMidiIn();
      (static_cast<RtMidiIn*>(port))->openVirtualPort(name);
    }
    catch (RtError& error) {
      throw new string(error.getMessage());
    }
  }
  else if (type == VMIDI_PORT_OUTPUT) {
    try {
      port = new RtMidiOut();
      (static_cast<RtMidiOut*>(port))->openVirtualPort(name);
    }
    catch (RtError& error) {
      throw new string(error.getMessage());
    }
  }

  portId = addPort(port, type);
}

void VMidiRtMidi::
destroyVirtualPort (VMidiPortType type, int portId) throw(string) {      

  if (type == VMIDI_PORT_INPUT) {
    destroyCallback(portId);
    void* port = removePort(portId, type);
    RtMidiIn* in = static_cast<RtMidiIn*>(port);
    in->cancelCallback();
    delete in;
  }
  else if (type == VMIDI_PORT_OUTPUT) {
    set<int> inputs;
    getInputs(inputs, portId);
  
    for (set<int>::iterator it = inputs.begin();
         it != inputs.end();
         it++) {
      int input = *it;
      map<int, VMidiRtMidiThruCallback*>::iterator i_cb = callbacks.find(input);
       
      // A callback exists for this input.
      if (i_cb != callbacks.end()) {
        // The input has no more connection : destroy the callback.
        if (isOrphan(input)) {
          destroyCallback(input);
          RtMidiIn* in = static_cast<RtMidiIn*>(getInputPortData(input));
          in->cancelCallback();
        }
        // The input still has connections left : remove the output from the callback.
        else {
          VMidiRtMidiThruCallback* cb = i_cb->second;
          cb->removeOutput(static_cast<RtMidiOut*>(getOutputPortData(portId)));
        }
      }
    }
    void* port = removePort(portId, type);
    delete static_cast<RtMidiOut*>(port);
  }
}

void VMidiRtMidi::
connectPorts (int srcPortId, int dstPortId) throw(string) {
  VMidiImplCommon::connectPorts(srcPortId, dstPortId);

  map<int, VMidiRtMidiThruCallback*>::iterator it = callbacks.find(srcPortId);

  // No callback registered yet.
  if (it == callbacks.end()) {
    set<RtMidiOut*>* outputs = new set<RtMidiOut*>;
    outputs->insert(static_cast<RtMidiOut*>(getOutputPortData(dstPortId)));
    VMidiRtMidiThruCallback* callback = new VMidiRtMidiThruCallback(*outputs);
    RtMidiIn* in = static_cast<RtMidiIn*>(getInputPortData(srcPortId));
    in->setCallback (rtMidiCallback, callback);
    callbacks[srcPortId] = callback;
  }
  else {
    VMidiRtMidiThruCallback* callback = it->second;
    callback->addOutput(static_cast<RtMidiOut*>(getOutputPortData(dstPortId)));
  }

}

void VMidiRtMidi::
disconnectPorts (int srcPortId, int dstPortId) throw(string) {
  VMidiImplCommon::disconnectPorts(srcPortId, dstPortId);

  if (isOrphan(srcPortId)) {
    destroyCallback(srcPortId);
    RtMidiIn* in = static_cast<RtMidiIn*>(getInputPortData(srcPortId));
    in->cancelCallback();
  }
  else {
    map<int, VMidiRtMidiThruCallback*>::iterator it = callbacks.find(srcPortId);
    if (it != callbacks.end()) {
      VMidiRtMidiThruCallback* callback = it->second;
      callback->removeOutput(static_cast<RtMidiOut*>(getOutputPortData(dstPortId)));
    }
  }
}

};

